/**
 * <p>文件名:		W3cXmlUtil.java</p>
 * <p>版权:		CopyrightTag</p>
 * <p>公司:		千方集团CTFO</p>
 * @author		周华彬(zhouhuabin@ctfo.com, zhou_hua_bin@163.com)
 */

package com.caits.lbs.framework.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.caits.lbs.framework.log.CommonLogFactory;

/**
 * <p>
 * W3cXmlUtil
 * </p>
 * <p>
 * 用途：W3C包的xml帮助工具类
 * </p>
 * 
 * @author 周华彬(zhouhuabin@ctfo.com, zhou_hua_bin@163.com)
 * @version 0.0.1 2011-7-1
 *          <table style="border:1px solid gray;">
 *          <tr>
 *          <th width="100px">版本号</th>
 *          <th width="100px">动作</th>
 *          <th width="100px">修改人</th>
 *          <th width="100px">修改时间</th>
 *          </tr>
 *          <!-- 以 Table 方式书写修改历史 -->
 *          <tr>
 *          <td>0.0.0</td>
 *          <td>创建类</td>
 *          <td>zhb</td>
 *          <td>2011-7-1 下午4:20:09</td>
 *          </tr>
 *          <tr>
 *          <td>0.0.1</td>
 *          <td>创建类</td>
 *          <td>zhb</td>
 *          <td>2011-7-1 下午4:20:09</td>
 *          <td>0.0.2</td>
 *          <td>修改类</td>
 *          <td>xxx</td>
 *          <td>x年x月x日</td>
 *          </tr>
 *          </table>
 */

public class W3cXmlUtil {

	/** 日志记录器 */
	static Logger log = CommonLogFactory.getLog();

	/** TODO */
	private static XPath xpath = XPathFactory.newInstance().newXPath();
	
	/**
	 * 当前Documnet写到输出流
	 * 
	 * @param doc
	 * @param os
	 * @param encoding
	 *            : 编码
	 * @param standalone
	 * @param sortAttrs
	 *            ：节点的属性是否按照字母排序
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public static boolean saveDocument(Document doc, OutputStream os,
			String encoding, boolean standalone, boolean sortAttrs)
			throws UnsupportedEncodingException {
		if (os == null)
			return false;
		try {
			Transformer transformer = TransformerFactory.newInstance()
					.newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			if (encoding != null && encoding.trim().length() != 0)
				transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
			transformer.setOutputProperty(OutputKeys.STANDALONE,
					standalone ? "yes" : "no");
			DOMSource source = new DOMSource((Node) doc);
			StreamResult result = new StreamResult(os);
			transformer.transform(source, result);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * 保存当前Documnet到文件
	 * 
	 * @param doc
	 * @param filename
	 * @param encoding
	 *            : 编码
	 * @param standalone
	 * @return
	 * @throws UnsupportedEncodingException
	 * @throws FileNotFoundException
	 */
	public static boolean saveDocument(Document doc, String filename,
			String encoding, boolean standalone)
			throws UnsupportedEncodingException, FileNotFoundException {
		if (doc == null || filename == null || filename.trim().length() == 0)
			return false;
		try {
			Transformer transformer = TransformerFactory.newInstance()
					.newTransformer();
			transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS,
					" ");
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			if (encoding != null && encoding.trim().length() != 0)
				transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
			transformer.setOutputProperty(OutputKeys.STANDALONE,
					standalone ? "yes" : "no");
			DOMSource source = new DOMSource((Node) doc);

			StreamResult result = new StreamResult(new FileOutputStream(
					filename));
			transformer.transform(source, result);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * w3c doc 转换为字符串
	 * 
	 * @param doc
	 * @param encoding
	 * @param standalone
	 * @return
	 */
	public static String docToString(Document doc, String encoding,
			boolean standalone) {
		try {
			Transformer transformer = TransformerFactory.newInstance()
					.newTransformer();
			transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS,
					" ");
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			if (encoding != null && encoding.trim().length() != 0)
				transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
			transformer.setOutputProperty(OutputKeys.STANDALONE,
					standalone ? "yes" : "no");
			DOMSource source = new DOMSource((Node) doc);
			StringWriter stringWriter = new StringWriter();
			StreamResult result = new StreamResult(stringWriter);
			transformer.transform(source, result);
			return stringWriter.getBuffer().toString();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 根据名称获得第一个节点
	 * @param parent
	 * @param name
	 * @return
	 */
	public static Node getElementByName(Element parent, String name) {
		if (name == null || name.trim().length() == 0)
			return null;
		NodeList nodelist = parent.getElementsByTagName(name);
		if (nodelist.getLength() >= 0)
			return nodelist.item(0);
		else
			return null;
	}

	/**
	 * select xml file item
	 * @param <T>
	 * @param refClass
	 * @param expression
	 * @param item: referance item
	 * @param returnType: return type
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T selectXmlItem(Class<T> refClass, String expression, Object item, QName returnType){
		if(expression == null || expression.trim().length() == 0 || item == null || returnType == null)
			return null;
		try {
			return (T)xpath.evaluate(expression, item, returnType);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return null;
	}
	/**
	 * 根据属性名取节点的属性值
	 * 
	 * @param node
	 * @param attrname
	 * @return
	 */
	public static String getAttrbuteValue(Node node, String attrname) {
		if (attrname == null || node == null)
			return "";
		if (hasAttributes(node) == false)
			return "";
		NamedNodeMap nodmap = node.getAttributes();
		for (int i = 0; i < nodmap.getLength(); i++) {
			if (attrname.equals(nodmap.item(i).getNodeName())) {
				return nodmap.item(i).getNodeValue();
			}
		}
		return "";
	}

	/**
	 * returns the text content of this node and its descendants
	 * 
	 * @param node
	 * @return
	 */
	public static String getTextContent(Node node) {
		if (node == null)
			return null;
		return node.getTextContent();
	}

	/**
	 * 判断是否有Attributes
	 * 
	 * @param node
	 * @return true: has; false:otherwise
	 */
	public static boolean hasAttributes(Node node) {
		if (node == null)
			return false;
		return node.hasAttributes();
	}

	/**
	 * 判断是否有childNodes
	 * 
	 * @param node
	 * @return true: has; false:otherwise
	 */
	public static boolean hasChildNodes(Node node) {
		if (node == null)
			return false;
		return node.hasChildNodes();
	}

	/**
	 * 删除所有children
	 * 
	 * @param opNode
	 */
	public static void removeAllChild(Node opNode) {
		if (opNode == null)
			return;
		if (opNode.hasChildNodes()) {
			NodeList childs = opNode.getChildNodes();
			if (childs != null) {
				for (int i = 0; i < childs.getLength(); i++)
					opNode.removeChild(childs.item(i));
			}
		}
	}

	/**
	 * 修改所有子节点的文本内容
	 * @param node
	 * @param name
	 * @param newValue
	 */
	public static void editValueByName(Node node, String name, String newValue) {
		if (name == null || node == null)
			return;
		NodeList nodelist = node.getChildNodes();
		if (nodelist == null)
			return;
		int len = nodelist.getLength();
		if (len <= 0)
			return;
		for (int i = 0; i < len; i++) {
			if (name.equals(nodelist.item(i).getNodeName()))
				nodelist.item(i).setTextContent(referTxtOrAttr(newValue));
		}
	}

	/**
	 * 根据名称修改所有节点的文本值
	 * @param root
	 * @param name
	 * @param newValue
	 */
	public static void editValueByName(Element root, String name,
			String newValue) {
		if (name == null || root == null)
			return;
		NodeList nodelist = root.getElementsByTagName(name);
		if (nodelist == null)
			return;
		int len = nodelist.getLength();
		if (len <= 0)
			return;
		for (int i = 0; i < len; i++) {
			nodelist.item(i).setTextContent(referTxtOrAttr(newValue));
		}
	}

	/**
	 * 修改一个Node的attribute值
	 * 
	 * @param node
	 * @param attrName
	 * @param newAttrValue
	 */
	public static void editAttrByAttrName(Node node, String attrName,
			String newAttrValue) {
		if (attrName == null || node == null || newAttrValue == null)
			return;
		if (hasAttributes(node) == false)
			return;
		NamedNodeMap nodmap = node.getAttributes();
		for (int i = 0; i < nodmap.getLength(); i++) {
			if (attrName.equals(nodmap.item(i).getNodeName())) {
				nodmap.item(i).setNodeValue(referTxtOrAttr(newAttrValue));
				break;
			}
		}
	}

	/**
	 * 修改一个Node的attribute值
	 * 
	 * @param element
	 * @param attrName
	 * @param newAttrValue
	 */
	public static void editAttrByAttrName(Element element, String attrName,
			String newAttrValue) {
		if (attrName == null || element == null || newAttrValue == null)
			return;
		Attr attr = element.getAttributeNode(attrName);
		if (attr == null)
			return;
		attr.setValue(referTxtOrAttr(newAttrValue));
	}

	/**
	 * 增加文本节点到现有节点
	 * @param doc
	 * @param parent
	 * @param nodeText
	 * @return
	 */
	public static Node addChildText(Document doc, Node parent, String nodeText) {
		if (parent == null || nodeText == null || doc == null)
			return null;
		Node node = doc.createTextNode(referTxtOrAttr(nodeText));
		return parent.appendChild(node);
	}

	/**
	 * 将节点列表追加到现有节点上
	 * @param parent
	 * @param newNodes
	 */
	public static void addChildNodes(Node parent, NodeList newNodes) {
		if (parent == null || newNodes == null)
			return;
		for (int i = 0; i < newNodes.getLength(); i++) {
			addChild(parent, newNodes.item(i));
		}
	}

	/**
	 * 根据map将属性附加到元素上
	 * @param ele
	 * @param attrmap
	 */
	public static void addAttribute(Element ele, Map<String, String> attrmap) {
		if (ele == null || attrmap == null || attrmap.isEmpty())
			return;
		for (String attrNmae : attrmap.keySet()) {
			ele.setAttribute(attrNmae, referTxtOrAttr(attrmap.get(attrNmae)));
		}
	}

	/**
	 * 给定文档和属性值属性名称创建属性 
	 * @param doc
	 * @param attrNmae
	 * @param value
	 * @return
	 */
	public static Attr createAttribute(Document doc, String attrNmae,
			String value) {
		Attr attr = doc.createAttribute(attrNmae);
		attr.setNodeValue(referTxtOrAttr(value));
		return attr;
	}

	/**
	 * 给元素设置属性
	 * @param ele
	 * @param attrNmae
	 * @param value
	 */
	public static void addAttribute(Element ele, String attrNmae, String value) {
		if (ele == null || attrNmae == null || value == null)
			return;
		ele.setAttribute(attrNmae, referTxtOrAttr(value));
	}

	/**
	 * 在现有节点增加子节点
	 * 
	 * @param doc
	 * @param parent
	 * @param nodeName
	 * @param nodeValue
	 * @return
	 */
	public static Node addChild(Document doc, Node parent, String nodeName,
			String nodeValue) {
		if (doc == null || parent == null || nodeName == null)
			return null;
		Node node = doc.createElement(nodeName);
		if (nodeValue != null && nodeValue.trim().length() != 0)
			node.setTextContent(referTxtOrAttr(nodeValue));// node.appendChild(doc.createTextNode(nodeValue));
		return parent.appendChild(node);
	}

	/**
	 * 在现有节点增加子节点
	 * 
	 * @param parent
	 * @param newNode
	 * @return
	 */
	public static Node addChild(Node parent, Node newNode) {
		if (parent == null || newNode == null)
			return null;
		return parent.appendChild(newNode);
	}

	/**
	 * 给文档增加元素
	 * 
	 * @param doc
	 * @param rootname
	 */
	public static Node addDocumentRoot(Document doc, String rootname) {
		Element root = doc.createElement(rootname);
		return doc.appendChild(root);
	}

	/**
	 * 创建新的空文档
	 * 
	 * @return
	 * @throws ParserConfigurationException
	 */
	public static Document createNewDocument()
			throws ParserConfigurationException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		return builder.newDocument();
	}

	/**
	 * 读取XML文件返回Document
	 * 
	 * @param is
	 * @return
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 */
	public static Document readDocumentFromXmlFile(InputStream is)
			throws ParserConfigurationException, SAXException, IOException {
		if (is == null)
			return null;
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder parser = factory.newDocumentBuilder();
		return parser.parse(is);
	}

	/**
	 * 读取XML文件返回Document
	 * 
	 * @param file
	 * @return
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 */
	public static Document readDocumentFromXmlFile(File file)
			throws ParserConfigurationException, SAXException, IOException {
		if (file == null)
			return null;
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder parser = factory.newDocumentBuilder();
		return parser.parse(file);
	}

	/**
	 * 读取XML文件返回Document
	 * 
	 * @param filepath
	 * @return
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 */
	public static Document readDocumentFromXmlFile(String filepath)
			throws ParserConfigurationException, SAXException, IOException {
		if (filepath == null || filepath.trim().length() == 0)
			return null;
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder parser = factory.newDocumentBuilder();
		if (filepath.startsWith("classpath:")) {
			String clspath = filepath.substring(10);
			InputStream is = W3cXmlUtil.class.getClassLoader()
					.getResourceAsStream(clspath);
			return parser.parse(is);
		} else if(filepath.startsWith("http:")||filepath.startsWith("file:")||filepath.startsWith("ftp:")) {
			return parser.parse(filepath);
		}else{
			InputStream is = new FileInputStream(filepath);
			return parser.parse(is);
		}
	}

	/**
	 * xml格式转转义
	 * 
	 * @param str
	 * @return
	 */
	static String referTxtOrAttr(String str) {
		if (str != null) {
			str = str.replaceAll("<", "&lt;");
			str = str.replaceAll(">", "&gt;");
			str = str.replaceAll("&", "&amp;");
			str = str.replaceAll("'", "&apos;");
			str = str.replaceAll("\"", "&quot;");
		}
		return str;
	}

	/**
	 * FIXME
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
	
	}

}
